/*
 * macro.c
 *
 *  Created on: Nov 5, 2016
 *      Author: root
 */

#include <stdio.h>
#include <stdlib.h>
#include "common.h"

static long gensymId = 0;

static int get_match_times_from_arg(Statement *arg, Queue *repQueue);
static Statement *expand(Statement *source, Queue *repQueue);

int get_match_times(Statement *loop, Queue *repQueue) {
	int matchTimes = 0;
	QueueNode *node = NULL;
	Statement *s = NULL;

	if (loop->children != NULL) {
		node = loop->children->head;
		while (node != NULL) {
			s = (Statement *) node->element;
			if (s->type == ARG) {
				matchTimes = get_match_times_from_arg(s, repQueue);
				if (matchTimes != 0)
					break;
			}
			node = node->next;
		}
	}
	return matchTimes;
}

static int get_match_times_from_arg(Statement *arg, Queue *repQueue) {
	int matchTimes = 0;
	QueueNode *node = NULL;
	Statement *s = NULL;
	Queue *loopQueue = NULL;
	String argName = NULL;

	if (arg->type == ARG) {
		argName = get_arg_name(arg);
		loopQueue = find_values(repQueue, argName);
		if (!is_queue_empty(loopQueue)) {
			matchTimes = count_queue_element(loopQueue);
		} else {
			if (arg->children != NULL) {
				node = arg->children->head;
				while (node != NULL) {
					s = (Statement *) node->element;
					if (s->type == ARG) {
						matchTimes = get_match_times_from_arg(s, repQueue);
						if (matchTimes != 0)
							break;
					}
					node = node->next;
				}
			}
		}
	}
	return matchTimes;
}

static Queue *expand_loop_statement(Statement *loop, Queue *repQueue, int matchTimes) {
	int i = 0;
	int index = 0;
	QueueNode *node = NULL;
	Statement *s = NULL;
	Statement *st = NULL;

	Queue *loopQueue = NULL;
	QueueNode *loopQueueNode = NULL;
	Queue *result = init_queue();
	String argName = NULL;

	for (i = 0; i < matchTimes; i++) {
		if (loop != NULL) {
			node = loop->children->head;
			while (node != NULL) {
				s = (Statement *) node->element;
				switch (s->type) {
				case SYM:
					enqueue(result, copy_statement(s));
					break;
				case ARG:
					argName = get_arg_name(s);
					loopQueue = find_values(repQueue, argName);
					if (argName != NULL) {
						if (!is_queue_empty(loopQueue)) {
							index = 0;
							loopQueueNode = loopQueue->head;
							while (loopQueueNode != NULL) {
								if (index == i) {
									st = (Statement *) get_queue_element(loopQueue, index);
									enqueue(result, copy_statement(st));
									break;
								}
								index++;
								loopQueueNode = loopQueueNode->next;
							}
						} else {
							enqueue(result, copy_statement(s));
						}
					} else {
						enqueue(result, expand(s, repQueue));
					}
					break;
				case LOOP:
					fprintf(stderr, "Loop in loop not support!");
					break;
				}

				node = node->next;
			}
		}
	}

	return result;
}

static Statement *expand(Statement *source, Queue *repQueue) {
	String argName = NULL;
	Statement *s = NULL;
	QueueNode *node = NULL;
	Statement *result = NULL;
	int matchTimes = 0;

	switch (source->type) {
	case SYM:
		result = copy_statement(source);
		break;

	case ARG:
		argName = get_arg_name(source);
		s = (Statement *) find_value(repQueue, argName);
		if (argName != NULL) {
			if (s != NULL)
				result = copy_statement(s);
			else
				result = copy_statement(source);
		} else {
			result = init_arg();
			node = source->children->head;
			while (node != NULL) {
				s = (Statement *) node->element;
				if (s->type == LOOP) {
					matchTimes = get_match_times(s, repQueue);
					append_queue(result->children, expand_loop_statement(s, repQueue, matchTimes));
				} else {
					enqueue(result->children, expand(s, repQueue));
				}
				node = node->next;
			}
		}
		break;

	case LOOP:
		fprintf(stderr, "Not support!");
		break;
	}

	return result;
}

static String generate_symbol(long baseId, Queue *gensymHolder, String symbol) {
	String name = alloc_string(40);
	int index = get_queue_element_index(gensymHolder, symbol, equals_string);
	if (index == -1) {
		snprintf(name, 40, "gensym-%ld", gensymId);
		gensymId++;
		enqueue(gensymHolder, symbol);
	} else {
		snprintf(name, 40, "gensym-%ld", (baseId + index));
	}
	return name;
}

static void replace_with_gensym(Statement *source) {
	QueueNode *node = NULL;
	Statement *st = NULL;
	Queue *gensymHolder = init_queue();
	long baseId = gensymId;
	String symbol = NULL;

	node = source->children->head;
	while (node != NULL) {
		st = (Statement *) node->element;
		if (st->type == ARG) {
			symbol = get_arg_name(st);
			if (symbol != NULL && *symbol == '$') {
				String name = generate_symbol(baseId, gensymHolder, symbol);
				set_arg_name(st, name);
			} else {
				replace_with_gensym(st);
			}
		}
		node = node->next;
	}
}

Statement *expand_statement(Mapping *mapping, Queue *repQueue) {
	Statement *s = expand(mapping->targetStatement, repQueue);
	replace_with_gensym(s);
	return s;
}

